package ldh.im.fxgui.controller.sub;

import com.fluxvend.svgfx.SvgImageView;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.WritableImage;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.robot.Robot;
import javafx.scene.shape.Rectangle;
import javafx.stage.Popup;
import javafx.stage.Stage;
import javafx.stage.Window;
import ldh.im.ImConfig;
import ldh.im.dto.enums.MessageType;
import ldh.im.fxbase.service.GifCreateService;
import ldh.im.fxbase.util.*;
import ldh.im.fxgui.component.ScreenSelectView;
import ldh.im.fxgui.pojo.RecordSettingData;
import ldh.im.util.ConfigUtil;
import ldh.im.util.RandomCharacterUtil;
import ldh.rpc.cache.CacheService;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.net.URL;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.ResourceBundle;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

public class RecordScreenController implements Initializable {

    private static final Logger LOGGER = LoggerFactory.getLogger(RecordScreenController.class);

    @FXML private Node root;
    @FXML private Label timeTip;
    @FXML private SvgImageView playRecordView;
    @FXML private SvgImageView recordTypeView;
    @FXML private SvgImageView selectAreaSuccessView;
    @FXML private SvgImageView selectAreaWarnView;
    @FXML private SvgImageView recordTypeSuccessView;
    @FXML private SvgImageView recordTypeWarnView;

    private Stage stage = null;
    private Consumer<Stage> closeCallback;
    private BiConsumer<String, MessageType> handleGifCallback;
    private Rectangle rectangle;
    private boolean isRecording = false;

    private ScheduledExecutorService scheduledService;
    private GifCreateService gifCreateService;
    private volatile FFmpegFrameRecorder recorder = null;
    private BufferedImage copyImage = null;
    private Java2DFrameConverter converter = null;

    private Date recordStartTime = null;
    private BooleanProperty selectAreaProperty = new SimpleBooleanProperty(false);
    private BooleanProperty recordTypeProperty = new SimpleBooleanProperty(false);

    private String filePath = null;      // 录制图片或mp4地址
    private RecordSettingData recordSettingData = null;
    private Popup settingPopup = null;

    public void setStage(Stage stage) {
        this.stage = stage;

        KeyCodeCombination sendButtonKey = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
        stage.getScene().getAccelerators().put(sendButtonKey, ()->{
            stopRecord();
        });
    }

    public void setCloseCallback(Consumer<Stage> closeConsumer) {
        this.closeCallback = closeConsumer;
    }

    public void setHandleGifCallback(BiConsumer<String, MessageType> handleGifCallback) {
        this.handleGifCallback = handleGifCallback;
    }

    @FXML
    public void playRecord(MouseEvent mouseEvent) {
        if (playRecordView.getSvg().endsWith("pause.svg")) {
            stopRecord();
            return;
        }
        if (rectangle == null) {
            DialogUtil.stageAlert("警告", "请选择录制局域");
            return;
        }
        if (rectangle.getWidth() < 10 && rectangle.getHeight() < 10) {
            DialogUtil.stageAlert("警告", "请选择录制局域");
            return;
        }
        if (recordSettingData == null) {
            DialogUtil.stageAlert("警告", "请设置录制类型");
            return;
        }
        playRecordView.setSvg(this.getClass().getResource("/im-svg/pause.svg").toExternalForm());

        double sceneX1 = stage.getX();
        double sceneY1 = stage.getY();

        Rectangle rectangle1 = new Rectangle(sceneX1, sceneY1, stage.getWidth(), stage.getHeight());

        boolean isIntersects = rectangle.intersects(rectangle1.getLayoutBounds());
        System.out.println("isIntersects:" + isIntersects);
        if (isIntersects) {
            stage.setAlwaysOnTop(false);
            stage.setIconified(true);
        }
        isRecording = true;
        recordStartTime = new Date();
        if(recordSettingData.getMessageType() == MessageType.Image) {
            recordToGif();
        } else {
            recordToMp4();
        }
    }

    public void closeEvent(ActionEvent actionEvent) {
        Window window = root.getScene().getWindow();
        if (window instanceof Stage) {
            ((Stage) window).close();
        }
        this.stage = null;
        if (closeCallback != null) {
            closeCallback.accept(stage);
        }
        closeCallback = null;
        handleGifCallback = null;
    }

    public void selectAreaEvent(MouseEvent mouseEvent) {
        Stage thisStage = (Stage) root.getScene().getWindow();
        thisStage.setIconified(true);
        ScreenSelectView screenSelectView = new ScreenSelectView(thisStage, this::selectRectangle);
        if (stage != null) {
            stage.setAlwaysOnTop(false);
            stage.setIconified(true);
        }
        if (rectangle != null) {
            screenSelectView.initRectangle(rectangle);
        }
        screenSelectView.show();
    }

    // 选择录制类型： 视频 or gif
    @FXML
    public void selectTypeEvent(MouseEvent e) {
        if (settingPopup == null) {
            settingPopup = new Popup();
            RegionUtil.FxmlBean<Pane, RecordScreenSettingController> fxmlBean =
                    RegionUtil.loadFxmlBean(this.getClass().getResource("/fxml/sub/RecordScreenSettingPane.fxml"));
            fxmlBean.getController().setPopup(settingPopup);
            fxmlBean.getController().setDataCallBack((recordSettingData -> {
                this.recordSettingData = recordSettingData;
                recordTypeProperty.set(true);
            }));
            settingPopup.setAutoHide(false);
            settingPopup.getContent().setAll(fxmlBean.getNode());
        }

        settingPopup.show(recordTypeView, e.getScreenX(), e.getScreenY()+40);
    }

    private void selectRectangle(Rectangle rectangle) {
        if (stage != null) {
            stage.setAlwaysOnTop(true);
            stage.setIconified(false);
        }
        this.rectangle = rectangle;
        LOGGER.info("rectangle info x: {}, y: {}, width: {}, height: {}", rectangle.getX(), rectangle.getY(),
                rectangle.getWidth(), rectangle.getHeight());
        selectAreaProperty.set(true);
    }

    private void recordToGif() {
        if (scheduledService != null) return;
        Robot robot = new Robot();
        scheduledService = Executors.newSingleThreadScheduledExecutor();
        filePath = buildFilePath();
        final long startTime = this.recordStartTime.getTime();
        gifCreateService = new GifCreateService(filePath, recordSettingData.getDelayTime());
        scheduledService.scheduleAtFixedRate(()->{
            Date now = new Date();
            long time = now.getTime() - startTime;
            if (!isRecording) {
                stopRecord();
                return;
            }
            boolean isEnd = false;
            if (time > recordSettingData.getRecordTotalTime() * 1000) {
                isEnd = true;
            }
            try {
                FxThreadUtil.runLater(()->{
                    timeTip.setText((time/1000) + "s");
                    WritableImage writableImage = robot.getScreenCapture(null, rectangle.getX(), rectangle.getY(),
                            rectangle.getWidth(),rectangle.getHeight(), false);
                    ThreadPoolUtil.submit(()->{
                        try {
                            BufferedImage bufferedImage = SwingFXUtils.fromFXImage(writableImage, null);
                            gifCreateService.writeImage(bufferedImage);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    });
                });
                if (isEnd) {
                    stopRecord();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, 0, recordSettingData.getDelayTime()-20, TimeUnit.MILLISECONDS);
    }

    private void recordToMp4() {
        if (scheduledService != null) return;
        Robot robot = new Robot();
        scheduledService = Executors.newSingleThreadScheduledExecutor();
        filePath = buildFilePath();
        LOGGER.info("record filePath {}", filePath);
        final long startTime = this.recordStartTime.getTime();

        scheduledService.scheduleAtFixedRate(()->{
            if (recorder == null) {
                startRecord();
            }

            Date now = new Date();
            long time = now.getTime() - startTime;
            if (!isRecording) {
                stopRecord();
                return;
            }
            boolean isEnd = false;
            if (time > recordSettingData.getRecordTotalTime() * 1000) {
                isEnd = true;
            }
            try {
                FxThreadUtil.runLater(()->{
                    Double t = time/1000.0;
                    DecimalFormat df = new DecimalFormat("#.0"); // 保留两位小数
                    String formattedNumber = df.format(t);
                    WritableImage writableImage = robot.getScreenCapture(null, rectangle.getX(), rectangle.getY(),
                            rectangle.getWidth(),rectangle.getHeight(), false);
                    timeTip.setText(formattedNumber + "s");

                    ThreadPoolUtil.submit(()->{
                        if(!isRecording) return;
                        try {
                            BufferedImage bufferedImage = SwingFXUtils.fromFXImage(writableImage, null);
                            Frame frame = grab(bufferedImage); // 将BufferedImage转换为Frame对象
                            recorder.record(frame);
//                            recorder.record(frame, avutil.AV_PIX_FMT_RGB32_1);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    });
                });
                if (isEnd) {
                    stopRecord();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, 0, 1000/recordSettingData.getFrameRate(), TimeUnit.MILLISECONDS);
    }

    private void showRecordPane() {
        if (!stage.isAlwaysOnTop()) {
            stage.setAlwaysOnTop(true);
            stage.setIconified(false);
        }
    }

    private void stopRecord() {
        if (!isRecording) return;
        if (recordSettingData.getMessageType() == MessageType.Image) {
            stopGifRecord();
        } else {
            stopMp4Record();
        }
    }

    private void stopGifRecord() {
        try {
            isRecording = false;

            gifCreateService.close();
            gifCreateService = null;
            FxThreadUtil.runLater(()->{
                showRecordPane();
                playRecordView.setSvg(this.getClass().getResource("/im-svg/play.svg").toExternalForm());
            });
            recordStartTime = null;

            if (scheduledService != null) {
                scheduledService.shutdownNow();
                scheduledService = null;
            }
            File file = new File(filePath);
            String newFilePath = filePath.substring(0, filePath.length()-4);
            file.renameTo(new File(newFilePath));
            if (handleGifCallback != null) {
                handleGifCallback.accept(newFilePath, recordSettingData.getMessageType());
            }
            filePath = null;
        } catch (Exception e) {
            LOGGER.error("stop record error", e);
        }
    }

    private void stopMp4Record() {
        try {
            isRecording = false;

            recorder.close();
            recorder = null;
            FxThreadUtil.runLater(()->{
                showRecordPane();
                playRecordView.setSvg(this.getClass().getResource("/im-svg/play.svg").toExternalForm());
            });
            recordStartTime = null;

            if (scheduledService != null) {
                scheduledService.shutdownNow();
                scheduledService = null;
            }
            File file = new File(filePath);
            String newFilePath = filePath.substring(0, filePath.length()-4);
            file.renameTo(new File(newFilePath));
            if (handleGifCallback != null) {
                handleGifCallback.accept(newFilePath, recordSettingData.getMessageType());
            }
            filePath = null;
            recordSettingData = null;
        } catch (Exception e) {
            LOGGER.error("stop record error", e);
        }
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // 消息发送按钮的快捷键
        KeyCodeCombination sendButtonKey = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
        StageUtil.getStage().getScene().getAccelerators().put(sendButtonKey, ()->{
            stopRecord();
        });

//        timeTip.textProperty().bind(timeProperty);
        selectAreaSuccessView.visibleProperty().bind(selectAreaProperty);
        selectAreaWarnView.visibleProperty().bind(selectAreaProperty.not());

        recordTypeSuccessView.visibleProperty().bind(recordTypeProperty);
        recordTypeWarnView.visibleProperty().bind(recordTypeProperty.not());
    }

    private String buildFilePath() {
        CacheService cacheService = RpcUtil.getRpcBootstrap().getRpcConfig().getCacheService();
        String userId = (String) cacheService.getCache(ImConfig.LOGIN_USER_ID);
        userId = userId == null ? "tmp" : userId;
        String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String imageCacheDir = ConfigUtil.getRoot() + "/capture";
        String dir = imageCacheDir + File.separator + userId + File.separator + dateStr;
        File imageCacheFile = new File(dir);
        if (!imageCacheFile.exists()) {
            imageCacheFile.mkdirs();
        }

        String suffix = recordSettingData.getMessageType() == MessageType.Image ? ".gif.tmp" : ".mp4.tmp";
        String filePath = dir + File.separator + System.currentTimeMillis() + RandomCharacterUtil.getRandomCharacter(5) + suffix;
        return filePath;
    }

    private void startRecord() {
        try {
            int width = (int)rectangle.getWidth();
            int height = (int) rectangle.getHeight();
            recorder = new FFmpegFrameRecorder(new File(filePath), width, height);
            recorder.setVideoOption("crf", "28");
            // 设置输出文件和分辨率
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // 设置视频编解码器
            recorder.setFormat("mp4"); // 设置容器格式
            recorder.setFrameRate(30); // 设置帧率
            recorder.setSampleRate(44100); // 设置采样率（对于音频）
            recorder.setGopSize(10); // 设置关键帧间隔（对于H264编码很重要）
            recorder.setPixelFormat(0);
            recorder.start();
        } catch (Exception e) {
            LOGGER.error("start record error", e);
        }

    }

    public Frame grab(BufferedImage image) {
        // 创建转换缓存，且图像类别使用TYPE_3BYTE_BGR。这是摄像头图像使用的格式
        // 默认获取的图像类别是TYPE_INT_RGB，这个直接转换后颜色会错误！
        // 另外，若对Frame使用OpenCV的图像处理接口（opencv_imgproc），一定要转换成这个类别，否则会报错！
        if (copyImage == null ||
                copyImage.getWidth() != image.getWidth() ||
                copyImage.getHeight() != image.getHeight()) {
            copyImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
        }
        // 将原图绘制到缓存上（实现拷贝和类别转换）
        Graphics g = copyImage.getGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        // 创建转换工具
        if (converter == null) {
            converter = new Java2DFrameConverter();
        }
        // 由于获取的图片没有进行Gamma处理，亮度会特别高，需要进行校正
        return converter.getFrame(copyImage, 1.0);
    }
}
